package com.lightsaber.trade.as.service.category.impl;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.lightsaber.trade.as.dao.category.CategoryBranchDao;
import com.lightsaber.trade.as.dao.category.CategoryLeafDao;
import com.lightsaber.trade.as.dao.category.CategoryNodeDao;
import com.lightsaber.trade.as.dao.planning.CategoryPlanningItemDao;
import com.lightsaber.trade.as.dao.planning.DisciplinePlanningItemDao;
import com.lightsaber.trade.as.entity.category.Category;
import com.lightsaber.trade.as.entity.category.CategoryBranch;
import com.lightsaber.trade.as.entity.category.CategoryLeaf;
import com.lightsaber.trade.as.entity.category.CategoryNode;
import com.lightsaber.trade.as.entity.category.CategoryProgrammeAttribute;
import com.lightsaber.trade.as.entity.planning.PlanningStatus;
import com.lightsaber.trade.as.service.category.CategoryNodeService;
import com.lightsaber.trade.as.service.category.CategoryProgrammeAttributeService;
import com.lightsaber.trade.as.service.param.ParamService;
import com.lightsaber.trade.core.dao.BaseDao;
import com.lightsaber.trade.core.entity.AuditedObject;
import com.lightsaber.trade.core.entity.BaseEntity;
import com.lightsaber.trade.core.service.BaseServiceImpl;

/**
 * @author jianliu
 * 
 */
@Component
@Service
@Transactional
public class CategoryNodeServiceImpl extends BaseServiceImpl<CategoryNode, String> implements
        CategoryNodeService {

    @Autowired
    private transient CategoryNodeDao categoryNodeDao;

    @Autowired
    private transient CategoryBranchDao categoryBranchDao;

    @Autowired
    private transient CategoryLeafDao categoryLeafDao;

    @Autowired
    private DisciplinePlanningItemDao disciplinePlanningItemDao;
    @Autowired
    private CategoryPlanningItemDao categoryPlanningItemDao;

    @Autowired
    private CategoryProgrammeAttributeService attService;
    private final static Logger log = LoggerFactory.getLogger(CategoryNodeServiceImpl.class);

    @Override
    public CategoryNode findById(final String id) {
        return categoryNodeDao.get(id);
    }

    @Override
    public void deleteById(final String id) throws Exception {
        CategoryNode categoryNode = findById(id);
        Category category = getCategoryByNode(categoryNode);
        boolean hasNotZero = true;
        if (StringUtils.isNotBlank(category.getBranchName())) {
            // branch tree
            hasNotZero = hasNotZeroByCategoryNode(categoryNode);
        } else {
            hasNotZero = hasNotZeroByMasterCategoryNode(categoryNode);
        }
        if (!hasNotZero) {
            categoryNodeDao.delete(id);
        } else {
            throw new Exception(
                    "Cann't delete this Category, it's relationship with other level planning ");
        }
    }

    private boolean hasNotZeroByMasterCategoryNode(final CategoryNode categoryNode) {

        if (categoryNode.getChildren().isEmpty()) {
            List<Category> categoryOwnerList = categoryNode.getOwnedBranchTrees();
            for (Category category : categoryOwnerList) {
                if (hasNotZeroByCategoryNode(category.getRoot())) {
                    return true;
                }
            }
        } else {
            for (CategoryNode child : categoryNode.getChildren()) {
                if (hasNotZeroByCategoryNode(child)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean hasNotZeroByCategoryNode(final CategoryNode categoryNode) {

        if (categoryNode.getIsLeafNode() == 1) {

            CategoryLeaf leaf = categoryLeafDao.get(categoryNode.getId());
            if (categoryPlanningItemDao.hasNotZeroByCategoryLeaf(leaf)) {
                return true;
            }

        } else {

            if (disciplinePlanningItemDao.hasNotZeroByCategoryNode(categoryNode)) {
                return true;
            }

            List<CategoryNode> children = categoryNode.getChildren();
            for (CategoryNode child : children) {

                if (hasNotZeroByCategoryNode(child)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public CategoryBranch findBranchById(final String id) {
        return categoryBranchDao.findUniqueBy("id", id);
    }

    @Override
    public CategoryLeaf findLeafById(final String id) {
        return categoryLeafDao.get(id);
    }

    @Override
    public CategoryNode findByCategoryId(final String categoryId) {
        return categoryNodeDao.findByCategoryId(categoryId);
    }

    @Override
    public void validateCategoryNode(final CategoryNode categoryNode) throws Exception {
        if (StringUtils.isBlank(categoryNode.getName())) {
            throw new Exception("’Node Name’ is required.");
        }

        if (categoryNode.getName().indexOf("'", 0) != -1) {
            throw new Exception("Category Node Name is unavailable.");
        }

        List<CategoryNode> list = categoryNodeDao.findByParentIdAndCategoryNodeName(categoryNode
                .getParent().getId(), categoryNode.getName());
        if (!list.isEmpty()) {
            if (StringUtils.isNotBlank(categoryNode.getId())) {
                if (!list.get(0).getId().equals(categoryNode.getId())) {
                    throw new Exception(
                            "'Node Name' exists under the same category tree path, please check.");
                }
            } else {
                throw new Exception(
                        "'Node Name' exists under the same category tree path, please check.");
            }
        }
        if (StringUtils.isNotBlank(categoryNode.getRemarks())) {
            if (categoryNode.getRemarks().length() > 4000) {
                throw new Exception("The maximum length of ‘Remark’ is 4000, please check.");
            }
        }
    }

    @Override
    public void saveLeafFromPage(final CategoryLeaf categoryNode) throws Exception {
        validateCategoryNode(categoryNode);
        if (StringUtils.isBlank(categoryNode.getId())) {
            // categoryNode.setCategory(null);
            categoryNode.setLevel2status(PlanningStatus.WaitingForSubmission);
            // categoryNode.seti
            categoryNodeDao.save(categoryNode);

            CategoryProgrammeAttribute att = new CategoryProgrammeAttribute();
            att.setCategoryLeaf(categoryNode);
            att.setProgrammeAttribute(ParamService.FUNDING_MODE);
            att.setValue(categoryNode.getFundingMode());
            attService.save(att);
        } else {
            CategoryLeaf old = findLeafById(categoryNode.getId());
            changeBranchRootNodeName(categoryNode, old);
            CategoryProgrammeAttribute att = attService.findByFundingMode(old);
            if (att != null) {
                att.setValue(categoryNode.getFundingMode());
                attService.save(att);
            } else {
                CategoryProgrammeAttribute attNew = new CategoryProgrammeAttribute();
                attNew.setCategoryLeaf(old);
                attNew.setProgrammeAttribute(ParamService.FUNDING_MODE);
                attNew.setValue(categoryNode.getFundingMode());
                attService.save(attNew);
            }
            BeanUtils.copyProperties(categoryNode, old, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, "parent", "category", AuditedObject.CREATED_BY,
                    AuditedObject.CREATED_DATE });
            categoryNodeDao.save(old);
        }
    }

    @Override
    public void saveLeaf(final CategoryLeaf categoryNode) throws Exception {
        if (StringUtils.isBlank(categoryNode.getId())) {
            categoryNode.setLevel2status(PlanningStatus.WaitingForSubmission);
            categoryNodeDao.save(categoryNode);
        } else {
            CategoryLeaf old = findLeafById(categoryNode.getId());
            BeanUtils.copyProperties(categoryNode, old, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, "parent", "category", AuditedObject.CREATED_BY,
                    AuditedObject.CREATED_DATE });
            categoryNodeDao.save(old);
        }
    }

    private void changeBranchRootNodeName(final CategoryNode categoryNode, final CategoryNode old)
            throws Exception {
        if (!old.getName().equals(categoryNode.getName())) {
            List<Category> categoryList = old.getOwnedBranchTrees();
            if (!categoryList.isEmpty()) {
                for (Category category : categoryList) {
                    CategoryNode root = category.getRoot();
                    root.setName(categoryNode.getName());
                    saveRoot(root);
                }
            }
        }
    }

    @Override
    public void saveBranch(final CategoryBranch categoryNode) throws Exception {
        validateCategoryNode(categoryNode);
        if (StringUtils.isBlank(categoryNode.getId())) {
            // categoryNode.setCategory(null);
            categoryNode.setLevel2status(PlanningStatus.WaitingForSubmission);
            categoryNodeDao.save(categoryNode);

        } else {
            CategoryBranch old = findBranchById(categoryNode.getId());
            if (old.getIsInheritParentNode() == 1 && categoryNode.getIsInheritParentNode() == 0) {
                changeCurrentInheritParentNodeFromYesToNo(old.getChildren(), categoryNode);
            } else if (old.getIsInheritParentNode() == 0
                    && categoryNode.getIsInheritParentNode() == 1) {
                changeCurrentInheritParentNodeFromNoToYes(old.getChildren(), categoryNode);
            }

            if (old.getIsDisciplineView() == 0 && categoryNode.getIsDisciplineView() == 1) {
                changeChildrenDisciplineView(old);
            }
            changeBranchRootNodeName(categoryNode, old);

            BeanUtils.copyProperties(categoryNode, old, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, "parent", "category", AuditedObject.CREATED_BY,
                    AuditedObject.CREATED_DATE });

            categoryNodeDao.save(old);

        }

    }

    private void changeChildrenDisciplineView(final CategoryNode currentNode) {
        if (currentNode.getChildren().isEmpty()) {
            return;
        } else {
            for (CategoryNode child : currentNode.getChildren()) {
                if (child.getIsDisciplineView() == 1) {
                    child.setIsDisciplineView(0);
                    categoryNodeDao.save(child);
                }
                changeChildrenDisciplineView(child);
            }
        }
    }

    @Override
    public void saveRoot(final CategoryNode categoryNode) throws Exception {

        if (StringUtils.isNotBlank(categoryNode.getRemarks())) {
            if (categoryNode.getRemarks().length() > 4000) {
                throw new Exception("The maximum length of ‘Remark’ is 4000, please check.");
            }
        }
        if (StringUtils.isBlank(categoryNode.getId())) {
            categoryNode.setLevel2status(PlanningStatus.WaitingForSubmission);
            categoryNode.setIsInheritParentNode(0);
            categoryNodeDao.save(categoryNode);
        } else {
            CategoryNode old = findById(categoryNode.getId());

            if (old.getIsDisciplineView() == 0 && categoryNode.getIsDisciplineView() == 1) {
                changeChildrenDisciplineView(old);
            }

            BeanUtils.copyProperties(categoryNode, old, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, "parent", "category", AuditedObject.CREATED_BY,
                    AuditedObject.CREATED_DATE, "name", "categoryOwners", "categoryPlanners",
                    "children", "ownedBranchTrees" });
            categoryNodeDao.save(old);
        }

    }

    public void changeCurrentInheritParentNodeFromNoToYes(final List<CategoryNode> children,
            final CategoryNode currentNode) {
        if (!children.isEmpty()) {
            for (int i = 0; i < children.size(); i++) {
                CategoryNode child = children.get(i);
                if (child.getIsInheritParentNode() == 1) {
                    child.setInputType(currentNode.getInputType());
                    child.setSourceType(currentNode.getSourceType());
                    // child.setPlannedPlaces(currentNode.getPlannedPlaces());
                    // child.setPlannedTrainingHours(currentNode.getPlannedTrainingHours());
                    categoryNodeDao.save(child);
                    changeCurrentInheritParentNodeFromNoToYes(child.getChildren(), currentNode);
                } else {
                    continue;
                }
            }
        }
    }

    public void changeCurrentInheritParentNodeFromYesToNo(final List<CategoryNode> children,
            final CategoryNode currentNode) {
        if (!children.isEmpty()) {
            for (int i = 0; i < children.size(); i++) {
                CategoryNode child = children.get(i);
                if (child.getIsInheritParentNode() == 1) {
                    child.setIsInheritParentNode(0);
                    categoryNodeDao.save(child);
                } else {
                    continue;
                }
            }
        }
    }

    @Override
    public void deleteAllNodeExceptRoot(final String id) {
        CategoryNode categoryNode = categoryNodeDao.get(id);
        List<CategoryNode> nodeList = categoryNode.getChildren();
        categoryNode.setChildren(null);
        for (CategoryNode node : nodeList) {
            categoryNodeDao.delete(node);
        }
    }

    @Override
    protected BaseDao<CategoryNode, String> getDao() {
        // TODO Auto-generated method stub
        return categoryNodeDao;
    }

    @Override
    public void changeParentNode(final String currentNodeId, final String parentId)
            throws Exception {
        CategoryNode node = findById(currentNodeId);

        if (currentNodeId.equals(parentId)) {
            throw new Exception("this selected node is current node");
        }

        CategoryNode parent = findById(parentId);
        if (parent.getIsLeafNode() == 1) {
            throw new Exception("this selected node is a leaf node");
        }

        List<CategoryNode> list = categoryNodeDao.findByParentIdAndCategoryNodeName(parentId,
                node.getName());
        if (!list.isEmpty()) {
            throw new Exception(
                    "'Node Name' exists under the same category tree path, please check.");
        }

        if (parent.getParent() != null) {
            if (!parent.getCategoryOwners().isEmpty()) {
                throw new Exception("this selected node has owners.");
            } else {
                if (moveToChildOrNot(false, node, parentId)) {
                    throw new Exception("this selected node belong to its child node.");
                }
            }
        }

        node.setParent(parent);
        if (node.getIsLeafNode() == 1) {
            if (node.getIsInheritParentNode() == 1) {
                node.setInputType(parent.getInputType());
                node.setSourceType(parent.getSourceType());
                // node.setPlannedPlaces(parent.getPlannedPlaces());
                // node.setPlannedTrainingHours(parent.getPlannedTrainingHours());
            }
            categoryNodeDao.save(node);
//            saveLeaf((CategoryLeaf) node);
        } else {
            if (node.getIsInheritParentNode() == 1) {
                if (node.getInputType().equals(parent.getInputType())
                        && node.getSourceType().equals(parent.getSourceType())) {

                } else {
                    node.setInputType(parent.getInputType());
                    node.setSourceType(parent.getSourceType());
                    changeCurrentInheritParentNodeFromNoToYes(node.getChildren(), parent);
                }
            }
            categoryNodeDao.save(node);
//            saveBranch((CategoryBranch) node);
        }
        // TODO
    }

    public boolean moveToChildOrNot(boolean moveToChild, final CategoryNode currentNode,
            final String moveToNodeId) {
        List<CategoryNode> children = currentNode.getChildren();
        if (children.isEmpty()) {
            return moveToChild;
        }

        for (int i = 0; i < children.size(); i++) {
            CategoryNode child = children.get(i);
            if (child.getId().equals(moveToNodeId)) {
                moveToChild = true;
                return moveToChild;
            }

            if (!child.getChildren().isEmpty()) {
                moveToChildOrNot(moveToChild, child, moveToNodeId);
            }
        }
        return moveToChild;
    }

    @Override
    public List<String> getTreePathOfNode(CategoryNode categoryNode) {
        List<String> nodePath = new LinkedList<String>();

        // Until no parent or category
        Category tree = null;
        while (categoryNode != null
                && (categoryNode.getParent() != null || categoryNode.getCategory() != null)) {

            // only has parent
            if (categoryNode.getParent() != null && categoryNode.getCategory() == null) {
                nodePath.add(categoryNode.getName());// Add into path
                categoryNode = categoryNode.getParent();
            }

            // only has category
            else if (categoryNode.getParent() == null && categoryNode.getCategory() != null) {

                tree = categoryNode.getCategory();
                if (tree.getOwnerNode() == null) {
                    nodePath.add(categoryNode.getName());// Add root into path
                }

                categoryNode = tree.getOwnerNode();
            }
        }

        Collections.reverse(nodePath);
        for (String name : nodePath) {
            log.debug("### Node name = {}", name);
        }
        return nodePath;
    }

    @Override
    public CategoryNode getNodeByPath(final List<String> nodePath, final Category masterTree) {

        if (nodePath == null || nodePath.size() <= 0) {
            return null;
        }

        CategoryNode root = masterTree.getRoot();
        if (!root.getName().equals(nodePath.get(0))) {
            return null;
        }

        if (nodePath.size() == 1) {
            return root;
        }

        CategoryNode node = root;

        for (int i = 1; i < nodePath.size(); i++) {
            String nodeName = nodePath.get(i);
            boolean isMatch = false;

            for (CategoryNode child : node.getChildren()) {
                if (child.getName().equals(nodeName)) {
                    node = child;
                    isMatch = true;
                    break;
                }
            }

            // If there is not any node match the path name, return null
            if (!isMatch) {
                return null;
            }
        }

        return node;
    }

    @Override
    public Category getCategoryByNode(final CategoryNode node) {
        CategoryNode parent = node.getParent();
        if (parent != null) {
            log.info("parent name :{}", parent.getName());
            return getCategoryByNode(parent);
        } else {
            return node.getCategory();
        }
    }
}
